﻿using System.Buffers.Binary;

namespace System;

public class BytesUtils
{
    private static readonly string WhiteSpace = " ";

    public static readonly ConcurrentDictionary<string, byte[]> MeterSNDict = new ConcurrentDictionary<string, byte[]>();

    public static ushort ToUshort(ReadOnlySpan<byte> bytes, bool isBigEndian = false)
    {
        if (isBigEndian)
            return BinaryPrimitives.ReadUInt16BigEndian(bytes);

        return BinaryPrimitives.ReadUInt16LittleEndian(bytes);
    }

    public static string ToBCDStr(ReadOnlySpan<byte> sourceBytes, bool isBigEndian = false)
    {
        var sb = new StringBuilder();

        if (isBigEndian)
        {
            for (int index = 0; index < sourceBytes.Length; index++)
                sb.Append(sourceBytes[index].ToString("X2"));
        }
        else
        {
            for (int index = sourceBytes.Length - 1; index >= 0; index--)
                sb.Append(sourceBytes[index].ToString("X2"));
        }
        return sb.ToString();
    }

    public static byte[] StrToBCD(string bcdStr, bool isBigEndian = false)
    {
        if (MeterSNDict.TryGetValue(bcdStr, out var result))
            return result;

        int len = bcdStr.Length / 2;
        byte[] bytes = new byte[len];

        for (int i = 0; i < len; i++)
            bytes[i] = Convert.ToByte(bcdStr.Substring(i * 2, 2), 16);
        if (isBigEndian == false)
            Array.Reverse(bytes);

        MeterSNDict.TryAdd(bcdStr, bytes);
        return bytes;
    }

    /// <summary>
    /// 事件转字节流(协议定制)
    /// </summary>
    /// <param name="time"></param>
    /// <returns></returns>
    public static byte[] TimeTo7Bytes(DateTime time)
    {
        return new byte[7] { (byte)time.DayOfWeek, (byte)time.Second, (byte)time.Minute, (byte)time.Hour, (byte)time.Day, (byte)time.Month, (byte)(time.Year % 100) };
    }

    /// <summary>
    /// ssmmHHddMMyy
    /// </summary>
    /// <param name="time"></param>
    /// <returns></returns>
    public static byte[] TimeTo6Bytes(DateTime time, int numberBase = 16)
    {
        string str = time.ToString("ssmmHHddMMyy");
        byte[] bytes = new byte[6];
        for (int i = 0; i < bytes.Length; i++)
        {
            bytes[i] = Convert.ToByte(str.Substring(i * 2, 2), numberBase);
        }
        return bytes;
    }

    public static byte[] DateTo3Bytes(DateTime time)
    {
        string str = time.ToString("HHddMM");
        byte[] bytes = new byte[6];
        for (int i = 0; i < bytes.Length; i++)
        {
            bytes[i] = Convert.ToByte(str.Substring(i * 2, 2), 16);
        }
        return bytes;
    }

    /// <summary>
    /// 检查校验和
    /// </summary>
    /// <param name="rawData">元数据</param>
    /// <param name="checkIndex">校验数据索引位置</param>
    /// <returns></returns>
    public static bool CheckSum(Span<byte> rawData, int checkIndex)
    {
        int len = rawData.Length;
        byte checkSum = 0;
        for (int i = 0; i < len; i++)
        {
            if (i == checkIndex)
                break;
            checkSum += rawData[i];
        }
        return checkSum == rawData[checkIndex];
    }

    /// <summary>
    /// 设置校验和
    /// </summary>
    /// <param name="rawData"></param>
    /// <param name="checkIndex"></param>
    /// <returns></returns>
    public static byte CalcCheckSum(Span<byte> rawData)
    {
        int len = rawData.Length;
        byte checkSum = 0;
        for (int i = 0; i < len; i++)
        {
            checkSum += rawData[i];
        }
        return checkSum;
    }

    /// <summary>
    /// 字节转字符串
    /// </summary>
    /// <param name="data"></param>
    /// <param name="insertSpace"></param>
    /// <returns></returns>
    public static string BytesToHexStr(Span<byte> data, bool insertSpace = false)
    {
        if (data == null)
            return string.Empty;

        StringBuilder builder = new StringBuilder();
        int len = data.Length;
        for (int i = 0; i < len; i++)
        {
            builder.AppendFormat("{0}", data[i].ToString("X2"));
            if (insertSpace)
                builder.Append(WhiteSpace);
        }
        return builder.ToString();
    }

    /// <summary>
    /// 字符串转字节
    /// </summary>
    /// <param name="hexs"></param>
    /// <returns></returns>
    public static byte[] HexStrToBytes(string hexs)
    {
        if (hexs.Contains(WhiteSpace))
            hexs = hexs.Replace(WhiteSpace, string.Empty);

        int len = hexs.Length / 2;
        byte[] data = new byte[len];
        for (int i = 0; i < len; i++)
            data[i] = Convert.ToByte(hexs.Substring(i * 2, 2), 16);

        return data;
    }

    public static byte[] IntTo3Bytes(int number, bool isBigEndian = false)
    {
        int num = 0;
        if (number < 0)
        {
            num = -number;
            num = ~Convert.ToInt32(num);
            num = num + 1;
        }
        else
            num = number;

        Span<byte> bytes = BitConverter.GetBytes(num);
        if (isBigEndian)
            return new byte[3] { bytes[2], bytes[1], bytes[0] };

        return bytes.Slice(0, 3).ToArray();
    }

    public static int ToInt32(Span<byte> data, bool isBigEndian = false)
    {
        if (data.Length == 3)
        {
            if (isBigEndian)
            {
                if (data[2] >= 0x80)
                    data = new byte[4] { 0xFF, data[0], data[1], data[2], };
                else
                    data = new byte[4] { 0, data[0], data[1], data[2] };
            }
            else
            {
                if (data[2] >= 0x80)
                    data = new byte[4] { data[0], data[1], data[2], 0xFF };
                else
                    data = new byte[4] { data[0], data[1], data[2], 0 };
            }
        }
        if (isBigEndian)
            return BinaryPrimitives.ReadInt32BigEndian(data);

        return BinaryPrimitives.ReadInt32LittleEndian(data);
    }


    public static decimal ThreeBytesToDecimal(Span<byte> data)
    {
        byte[] d = data.ToArray();
        Array.Reverse(d);
        if (d[0] >= 0x80)
        {
            d[0] = (byte)(Convert.ToInt32(d[0]) ^ Convert.ToInt32(0x80));
            return d.ToHexString().ToInt32() * -1;
        }
        return d.ToHexString().ToInt32();
    }

    /// <summary>
    /// 将字符串转成ASCII码并按指定长度右补齐
    /// </summary>
    /// <param name="len"></param>
    /// <param name="str"></param>
    /// <param name="reByte"></param>
    /// <returns></returns>
    public static byte[] ASCIIPadingRight(int len, string str, byte reByte)
    {
        List<byte> list = new List<byte>(len);
        byte[] strData = Encoding.ASCII.GetBytes(str);
        list.AddRange(strData);
        int todoLen = len - strData.Length;
        if (reByte == 0)
        {
            list.AddRange(new byte[todoLen]);
        }
        else
        {
            var todoData = new byte[todoLen];
            for (int i = 0; i < todoLen; i++)
                todoData[i] = reByte;
            list.AddRange(new byte[todoLen]);
        }
        return list.ToArray();
    }

    /// <summary>
    /// 将ASCII字节去掉0x00后转为ASCII码
    /// </summary>
    /// <param name="byteArray"></param>
    /// <returns></returns>
    public static string GetASCIICode(Span<byte> byteArray)
    {
        List<byte> list = new List<byte>();
        bool isEnd = true;
        for (int i = byteArray.Length - 1; i >= 0; i--)
        {
            if (byteArray[i] == 0x00 || byteArray[i] == 0xFF)
            {
                if (!isEnd)
                    list.Add(byteArray[i]);
            }
            else
            {
                isEnd = false;
                list.Add(byteArray[i]);
            }
        }
        list.Reverse();
        return Encoding.ASCII.GetString(list.ToArray());
    }

    /// <summary>
    /// CRC16校验和
    /// </summary>
    /// <param name="data"></param>
    /// <param name="offset"></param>
    /// <param name="size"></param>
    /// <returns></returns>
    public static byte[] ValidateCRC16(Span<byte> data, int offset, int size, bool isBigEndian = false)
    {
        ushort crc = 0xFFFF;

        for (int i = offset; i < size; i++) 
        {
            crc ^= data[i];
            for (int cur = 0; cur < 8; cur++)
            {
                if ((crc & 0x0001) > 0)
                {
                    crc >>= 1;
                    crc ^= 0xA001;
                }
                else
                    crc >>= 1;
            }
        }
        return crc.ToBytes(isBigEndian);
    }
}